package com.se.http;

import com.util.DateUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.WebSocket;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;

/**
 * Created by hdu_huang on 2018/11/9.
 * 基于JDK11的HttpClient   支持WebSocket
 * 它没有对4xx或5xx的状态码抛出异常，需要自己根据情况来处理，手动检测状态码抛出异常或者返回其他内容
 */
@Slf4j
public class HttpV2Client {

    private static final HttpClient client;
    private static final int READ_TIMEOUT_MILLIS = Integer.getInteger("simple.v2.http.read.timeout", 5000);
    private static final int CONNECT_TIMEOUT_MILLIS = Integer.getInteger("simple.v2.http.connect.timeout", 3000);

    public static final String Default_Charset = StandardCharsets.UTF_8.name();

    static {
        client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).connectTimeout(Duration.ofSeconds(CONNECT_TIMEOUT_MILLIS)).build();
    }

    public static HashMap<String, String> builderHeaders() {
        HashMap<String, String> headers = new HashMap<>();
        //IllegalArgumentException: restricted header name: "Connection"  "Host"    java12 java -Djdk.httpclient.allowRestrictedHeaders=host ...
        //headers.put("Connection", "Keep-Alive");
        //headers.put("Host", "JDK11-Client");
        headers.put("RequestId", UUID.randomUUID().toString());
        headers.put("Request-Module", "JDK11-Client");
        return headers;
    }

    public static HttpResult get(@NonNull String url, Map<String, String> headers, Map<String, String> params) throws Exception {
        return get(url, headers, params, Default_Charset, READ_TIMEOUT_MILLIS);
    }

    public static HttpResult get(@NonNull String url, Map<String, String> headers, Map<String, String> params, @NonNull String encoding, int readTimeout) throws Exception {

        readTimeout = readTimeout > 0 ? readTimeout : READ_TIMEOUT_MILLIS;

        String content = HttpV1Client.encodingParams(params, encoding);
        url = url + "?" + content;

        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.GET().uri(URI.create(url)).timeout(Duration.ofSeconds(readTimeout));

        if (headers == null) {
            headers = builderHeaders();
        }
        headers.put("Accept-Charset", encoding);
        headers.forEach(builder::setHeader);

        return getHttpResult(builder);
    }

    private static HttpResult getAsync(String url, Map<String, String> headers, Map<String, String> params, String encoding, int readTimeout) throws Exception {
        return null;
    }

    public static HttpResult postForm(String url, Map<String, String> headers, Map<String, String> params) throws Exception{
        return postForm(url, headers, params, Default_Charset, READ_TIMEOUT_MILLIS);
    }

    public static HttpResult postForm(String url, Map<String, String> headers, Map<String, String> params, String encoding, int readTimeout) throws Exception{

        readTimeout = readTimeout > 0 ? readTimeout : READ_TIMEOUT_MILLIS;

        String content = HttpV1Client.encodingParams(params, encoding);
        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.POST(HttpRequest.BodyPublishers.ofString(content)).uri(URI.create(url)).timeout(Duration.ofSeconds(readTimeout));

        if (headers == null) {
            headers = builderHeaders();
        }
        headers.put("Accept-Charset", encoding);
        headers.put("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding);
        headers.forEach(builder::setHeader);

        return getHttpResult(builder);
    }

    public static HttpResult postJson(String url, Map<String, String> headers, String params) throws Exception{
        if (headers == null) {
            headers = builderHeaders();
        }

        headers.put("Content-Type", "application/json"); //默认json

        return requestRaw(url, headers, params, Default_Charset, READ_TIMEOUT_MILLIS);
    }

    public static HttpResult postXml(String url, Map<String, String> headers, String params) throws Exception{

        if (headers == null) {
            headers = builderHeaders();
        }

        headers.put("Content-Type", "application/xml"); //xml
        return requestRaw(url, headers, params, Default_Charset, READ_TIMEOUT_MILLIS);
    }

    public static HttpResult requestRaw(@NonNull String url, Map<String, String> headers, @NonNull String params, String encoding, int readTimeout) throws Exception {

        readTimeout = readTimeout > 0 ? readTimeout : READ_TIMEOUT_MILLIS;
        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.POST(HttpRequest.BodyPublishers.ofString(params)).uri(URI.create(url)).timeout(Duration.ofSeconds(readTimeout));

        if (headers == null) {
            headers = builderHeaders();
        }
        headers.put("Accept-Charset", encoding);
        headers.forEach(builder::setHeader);

        return getHttpResult(builder);
    }

    public static HttpResult getHttpResult(HttpRequest.Builder builder) throws Exception {

        var request = builder.build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        log.info(response.body());
        log.info("statusCode = {}", response.statusCode());

        Map<String, String> resHeaders = new HashMap<>(response.headers().map().size());
        for (Map.Entry<String, List<String>> entry : response.headers().map().entrySet()) {
            resHeaders.put(entry.getKey(), entry.getValue().get(0));
        }

        return new HttpResult(response.statusCode(), response.body(), resHeaders);
    }


    //HttpRequest BodyPublisher   HttpRequest.BodyPublishers
    //HttpResponse BodySubscriber  HttpResponse.BodyHandlers
    //还是弱鸡
    public static void main(String[] args) throws Exception{

        //CompletionStage CompletableFuture

        HttpResult httpResult = get("http://localhost:8080/hello", null, null, "UTF-8", -1);

        //HttpResult httpResult1 = postForm("https://httpbin.org/post?love=qian", null, null);

//        httpDemo();
//        httpGetSync();
//        httpPostSync();
//        httpAsync();
//        webSocket();

//        log.info("main1");
//        concurrentRequests();
//        log.info("main2");

//        CompletableFuture.completedFuture(DateUtil.getDelayValue(1000, "100")).thenAcceptAsync(log::info);
//        CompletableFuture.supplyAsync(() -> DateUtil.getDelayValue(1000, "100")).thenAccept(log::info);
//        log.info("main3");

        DateUtil.sleep(5000);
    }

    private static void httpDemo() throws Exception{

        var url = "https://baimi529.bmznkj.com/avappservice/session/share";
        var url1 = "http://localhost:8080/hello";

        var request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .GET().build();

        // 同步
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
        log.info(response.body());

        // 异步
        client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenApply(HttpResponse::body)
                .thenAccept(log::info).join();
    }

    private static void httpGetSync() throws Exception{

        // HttpRequest.BodyPublishers noBody
        HttpRequest requestGet = HttpRequest.newBuilder().uri(URI.create("http://httpbin.org/get")).timeout(Duration.ofSeconds(2)).header("UserId", "Huang")
                .build();
        HttpResponse<String> httpGet = client.send(requestGet, HttpResponse.BodyHandlers.ofString());
        log.info(httpGet.body());
        log.info("statusCode = {}", httpGet.statusCode());

        String cityUrl = "https://www.bmznkj.com/avappservice/v1/area/cityList";
        HttpRequest requestGet2 = HttpRequest.newBuilder().uri(URI.create(cityUrl + "?provinceId=1")).build();
        HttpResponse<String> httpGet2 = client.send(requestGet2, HttpResponse.BodyHandlers.ofString());
        log.info(httpGet2.body());

        //延迟获取 HttpTimeoutException: request timed out
        HttpRequest requestGet3 = HttpRequest.newBuilder().uri(URI.create("http://httpbin.org/delay/2")).timeout(Duration.ofSeconds(3)).header("UserId", "Huang")
                .build();
        HttpResponse<String> httpGet3 = client.send(requestGet3, HttpResponse.BodyHandlers.ofString());
        log.info(httpGet3.body());
    }

    private static void httpPostSync() throws Exception{

        //Post delay 1s
        String formUser = "name=李云&age=23";
        HttpRequest requestPost = HttpRequest.newBuilder().uri(URI.create("http://127.0.0.1:7001/post/user")).timeout(Duration.ofSeconds(2)).header("UserId", "Huang")
                .POST(HttpRequest.BodyPublishers.ofString(formUser))
                .build();

        HttpResponse<String> httpPost = client.send(requestPost, HttpResponse.BodyHandlers.ofString());
        log.info(httpPost.body());

        // json 必须设置 header
        String jsonUser = "{\"name\": \"丽云\", \"age\": 23, \"birthday\": \"1995-5-17 12:00:00\"}";
        HttpRequest requestPost2 = HttpRequest.newBuilder().uri(URI.create("http://127.0.0.1:7001/json/user"))
                .header("Content-Type", "application/json;charset=UTF-8")
                .POST(HttpRequest.BodyPublishers.ofString(jsonUser))
                .build();
        HttpResponse<String> httpGet2 = client.send(requestPost2, HttpResponse.BodyHandlers.ofString());
        log.info(httpGet2.body());
    }

    private static void httpAsync() throws Exception{

        // HttpRequest default get
        HttpRequest requestGet = HttpRequest.newBuilder().uri(URI.create("http://httpbin.org/get")).timeout(Duration.ofSeconds(2)).header("UserId", "Huang").build();
        client.sendAsync(requestGet, HttpResponse.BodyHandlers.ofString()).thenAccept(stringHttpResponse -> log.info(stringHttpResponse.body())).join();


        HttpRequest requestPost = HttpRequest.newBuilder().uri(URI.create("http://httpbin.org/delay/2")).build();
        client.sendAsync(requestPost, HttpResponse.BodyHandlers.ofString()).thenAccept(stringHttpResponse -> log.info(stringHttpResponse.body())).join();
    }

    private static void webSocket() {

        //ws由http升级而来:
        //HTTP/1.1 101 \r\n
        //Set-Cookie: JSESSIONID=FCF22E15B6F316A3BACA510FB4D0B168; Path=/; HttpOnly\r\n
        //Upgrade: websocket\r\n
        //Connection: upgrade\r\n
        //Sec-WebSocket-Accept: DstZXTRXkDTVrZ0vw/Cb1Sv/mhM=\r\n
        WebSocket webSocket = client.newWebSocketBuilder().buildAsync(URI.create("ws://192.168.1.183:7001/ws/1000"), new WebSocket.Listener() {

            @Override
            public void onError(WebSocket webSocket, Throwable error) {
                log.error("ws error : ", error);
            }

            @Override
            public CompletionStage<?> onText(WebSocket webSocket, CharSequence data, boolean last) {
                webSocket.request(1);
                log.info(data.toString());
                return CompletableFuture.completedFuture(data);
            }

        }).join();

        // last = true 表示一条消息，false 则和上一条一起发送
        webSocket.sendText("Hello World!", true).join();

        webSocket.sendText("你好！", true).join();

        webSocket.sendClose(WebSocket.NORMAL_CLOSURE, "ok").join();
    }

    // 并发请求 /stream().parallel() 并行
    private static void concurrentRequests() {

        log.info("concurrentRequests");

        var list = List.of("http://httpbin.org/delay/3", "http://httpbin.org/delay/4", "http://httpbin.org/delay/3");

        //list.replaceAll[返回类型相同] /stream.map 可以不同
        //可以一个map链完成
        var requests = list.stream().map(url -> HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(10)).header("UserId", "Huang").build()).collect(Collectors.toList());

        List<CompletableFuture<HttpResponse<String>>> futures = requests.stream().map(httpRequest -> client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())).collect(Collectors.toList());

        //Promise.all 阻塞main
//        CompletableFuture.allOf(futures.toArray(CompletableFuture<?>[]::new)).join();

        futures.forEach(future -> future.whenComplete((stringHttpResponse, throwable) -> {
            if (throwable != null) {
                log.error("error = {}", throwable);
                return;
            }

            log.info(stringHttpResponse.body());

        }));

        //worker-3 worker-5.. 没有allOf 会默认使用一个线程worker-3  join阻塞当前线程
        CompletableFuture.allOf(futures.toArray(CompletableFuture<?>[]::new)).join();

    }

}
